
#include "UsbLcd.h"
#include "hd44780_charset.h"
#include "ftdi.h"

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int current_row, current_col;
int current_pos_known;
int backlight_;

struct ftdi_context ctx;

enum reg { ir=0, dr=1 };
void UsbLcd_send(enum reg r, uint8_t d, int delay_us); /*delay_us=41*/
void check_error(int rc);

static uint8_t encode(int backlight, unsigned int r, int rnw,
		      int en, unsigned int d4)
{
  return (backlight?(1<<7):0) | (r<<4) | (rnw?(1<<5):0)
    | (en?(1<<6):0) | d4;
}

void UsbLcd_deinit()
{
  check_error( ftdi_usb_close(&ctx) );
  ftdi_deinit(&ctx);
}


void UsbLcd_init(int vendor, int product)
{

  check_error( ftdi_init(&ctx) );
  check_error( ftdi_usb_open(&ctx, vendor, product) );
  check_error( ftdi_enable_bitbang(&ctx, 0xFF) );
  check_error( ftdi_set_baudrate(&ctx, 192000) );

/*Data length=4bits, 4 Display lines, 5x10 font*/
UsbLcd_send(ir,0x28, 50000); 

UsbLcd_set_mode(1,0,0);

usleep(10000); 

UsbLcd_clear();

usleep(80000); 

/* Entry mode + shift */
UsbLcd_send(ir,0x06, 100);

usleep(10000); 

init_hd44780A00_charmap();


 UsbLcd_define_char(0,
	      "     ",
	      "     ",
	      "  ## ",
	      " #  #",
	      " #   ",
	      "###  ",
	      " #   ",
	      " #   ");

  UsbLcd_define_char(1,
	      "     ",
	      "     ",
	      " ####",
	      "#   #",
	      "#   #",
	      " ####",
	      "    #",
	      " ### ");
	      
  UsbLcd_define_char(2,
	      "   # ",
	      "     ",
	      "  ## ",
	      "   # ",
	      "   # ",
	      "   # ",
	      "#  # ",
	      " ##  ");
	      
  UsbLcd_define_char(3,
	      "     ",
	      "     ",
	      "#### ",
	      "#   #",
	      "#   #",
	      "#### ",
	      "#    ",
	      "#    ");
	      
  UsbLcd_define_char(4,
	      "     ",
	      "     ",
	      " ####",
	      "#   #",
	      "#   #",
	      " ####",
	      "    #",
	      "    #");
	      
  UsbLcd_define_char(5,
	      "     ",
	      "     ",
	      "#   #",
	      "#   #",
	      "#   #",
	      " ####",
	      "    #",
	      " ### ");
	      
  UsbLcd_define_char(6,
	      "     ",
	      "     ",
	      " #   ",
	      "# # #",
	      "   # ",
	      "     ",
	      "     ",
	      "     ");

  UsbLcd_define_char(7,
	      "     ",
	      "#    ",
	      " #   ",
	      "  #  ",
	      "   # ",
	      "    #",
	      "     ",
	      "     "); 

 UsbLcd_clear();

}


void UsbLcd_clear(void)
{
  UsbLcd_send(ir,0x01,100000);
  current_row=0;
  current_col=0;
  current_pos_known=1;
}


void UsbLcd_home(void)
{
  UsbLcd_send(ir,0x02,1520);
  current_row=0;
  current_col=0;
  current_pos_known=1;
}


void UsbLcd_set_mode(int enable_display,
		      int enable_line_cursor,
		      int enable_block_cursor)
{
  UsbLcd_send(ir,
       0x8 | (enable_display?0x4:0) | (enable_line_cursor?0x2:0)
       | (enable_block_cursor?0x1:0), 100 );
}


void UsbLcd_set_pos(int row, int col)
{
  int addr;

  if (current_pos_known
      && (current_row==row)
      && (current_col==col)) {
    return;
  }
  addr = col;
  switch(row) {
  case 0: break;
  case 1: addr+=0x40; break;
  case 2: addr+=0x14; break;
  case 3: addr+=0x54; break;
  }
  UsbLcd_send(ir,0x80 + addr, 41);
  current_row=row;
  current_col=col;
  current_pos_known=1;
}

void UsbLcd_write(char * s)
{
  size_t i;
  for(i = 0; i<strlen(s); ++i) {
    UsbLcd_send(dr,latin1_to_hd44780A00(s[i]), 41);
    current_col++;
  }
  if (current_col>=UsbLcd_cols()) {
    current_pos_known=0;
  }
}


void UsbLcd_write_at(int row, int col, char * s)
{
  UsbLcd_set_pos(row,col);
  UsbLcd_write(s);
}


static uint8_t charline_to_uint8(char * s)
{
  uint8_t v=0;
  int i;
  for(i=0; i<5; i++) {
    if (s[i]!=' ') {
      v += 1<<(4-i);
    }
  }
  return v;
}

void UsbLcd_define_char(int code, char * l1, char * l2, char * l3,
			 char * l4, char * l5, char * l6, char * l7,
			 char * l8)
{
  uint8_t cgram_addr = code<<3;
  UsbLcd_send(ir, 0x40 | cgram_addr, 41);
  UsbLcd_send(dr, charline_to_uint8(l1), 41);
  UsbLcd_send(dr, charline_to_uint8(l2), 41);
  UsbLcd_send(dr, charline_to_uint8(l3), 41);
  UsbLcd_send(dr, charline_to_uint8(l4), 41);
  UsbLcd_send(dr, charline_to_uint8(l5), 41);
  UsbLcd_send(dr, charline_to_uint8(l6), 41);
  UsbLcd_send(dr, charline_to_uint8(l7), 41);
  UsbLcd_send(dr, charline_to_uint8(l8), 41);
  if (current_pos_known) {
    UsbLcd_set_pos(current_row, current_col);
  }
}


void UsbLcd_backlight(int enable)
{
  uint8_t data;

  if (backlight_!=enable) {
    backlight_=enable;
    data = encode(backlight_,dr,0,0,0);
    check_error( ftdi_write_data(&ctx,&data,1) );
  }
}


int UsbLcd_rows(void) 
{
  return 4;
}


int UsbLcd_cols(void) 
{
  return 20;
}


void UsbLcd_send(enum reg r, uint8_t d, int delay_us)
{
  uint8_t v[6];
  int i;

  v[0] = encode(backlight_,r,0,0,(d>>4)),
  v[1] = encode(backlight_,r,0,1 ,(d>>4)),
  v[2] = encode(backlight_,r,0,0,(d>>4)),
  v[3] = encode(backlight_,r,0,0,(d&0xf)),
  v[4] = encode(backlight_,r,0,1 ,(d&0xf)),
  v[5] = encode(backlight_,r,0,0,(d&0xf));

/*printf("%d\n",&v[5]-&v[0]);*/

  ftdi_write_data(&ctx,v,6);

 if (delay_us>41) {
   /* long delays, rare */
   usleep(delay_us);
} else {
   /* short delay, 41 us is most common */
   while (delay_us) {
     for (i=0; i<50; ++i) {
     }
     delay_us--;
  }
}
}

void check_error(int rc)
{
  if (rc<0) {
   printf("check_error: code=%d\n", rc);
   if (ctx.error_str) {
      printf("check_error: string=%s\n", ctx.error_str);
   }
   exit(EXIT_FAILURE);
  }
}

